﻿using System;
using System.Collections.Generic;

namespace LazyMapper.Core
{
    internal interface IListMapper 
    {
        void Sync(object fromList, object toList);
        void SetMapper(MapperInstance map);
    }

    public class ListMapper<TListType> : ListMapper<TListType, TListType>
    {
        public ListMapper()
        {
            SetMapping(DefaultMapping);
        }

        private static TListType DefaultMapping(TListType arg1, TListType arg2)
        {
            return arg1;
        }
    }

    public class ListMapper<TInputType, TListType> : IListMapper
    {
        private Func<TInputType, TListType, bool> _equality;
        private Func<TInputType, TListType, TListType> _mappingAction;
        private ISorter<TListType> _sorter;
        private bool _skipDelete;
        private Func<MapType, TInputType, TListType, bool> _preMappingFunction;
        private Action<MapType, TInputType, TListType> _postMappingFunction;

        public ListMapper<TInputType, TListType> SetEquality(Func<TInputType, TListType, bool> equality)
        {
            _equality = equality;
            return this;
        }

        public ListMapper<TInputType, TListType> SetMapping(Func<TInputType, TListType, TListType> action)
        {
            _mappingAction = action;
            return this;
        }

         public void Sync(object fromList, object toList)
        {
            Sync((IEnumerable<TInputType>)fromList, (IList<TListType>)toList);
        }

        public void SetMapper(MapperInstance map)
        {
            SetMapping(map.Map);
        }

        public ListMapper<TInputType, TListType> PreMapping(Func<MapType, TInputType, TListType, bool> preMappingFunction)
        {
            _preMappingFunction = preMappingFunction;
            return this;
        }

        public ListMapper<TInputType, TListType> PostMapping(Action<MapType, TInputType, TListType> postMappingFunction)
        {
            _postMappingFunction = postMappingFunction;
            return this;
        }

        public ListMapper<TInputType, TListType> Sync(IEnumerable<TInputType> fromList, ICollection<TListType> toList)
        {
            Validate();
            var listWasBlank = toList.Count == 0;
            var itemsThatMightNotExist = new List<TListType>(toList);
            var i = 0;
            foreach (var fromItem in fromList)
            {
                var found = false;
                if (!listWasBlank)
                {
                    foreach (var toListItem in toList)
                    {
                        found = ItemEquality(fromItem, toListItem);
                        if (found)
                        {
                            MapExistingItems(fromItem, toListItem);
                            itemsThatMightNotExist.Remove(toListItem);
                            break;
                        }
                    }

                }
                if (!found)
                {
                    AddMap(toList, fromItem);
                }
                i++;
            }
            if (i == 0 && !_skipDelete)
            {
                toList.Clear();
            }
            else
            {
                if (!listWasBlank && !_skipDelete)
                {
                    foreach (var listType in itemsThatMightNotExist)
                    {
                        RemoveItem(toList, listType);
                    }
                }
                if (_sorter != null)
                {
                    var listTypes = toList as IList<TListType>;
                    if (listTypes == null) throw new Exception("Sync to must be of type List<> to enable sorting");
                    _sorter.Sort(listTypes);
                }
            }


            return this;
        }


        public ListMapper<TInputType, TListType> SortBy(ISorter<TListType> sorter)
        {
            _sorter = sorter;
            
            return this;
        }

        public ListMapper<TInputType, TListType> SkipDelete()
        {
            _skipDelete = true;
            return this;
        }

        #region Private Methods


        private void Validate()
        {
            if (_mappingAction == null) throw new Exception("Please set a mapper for this system");
        }

        protected virtual void RemoveItem(ICollection<TListType> toList, TListType listType)
        {
            var allow = true;
            if (_preMappingFunction != null) allow = _preMappingFunction(MapType.Remove, default(TInputType), listType);
            if (allow)
            {
                toList.Remove(listType);
                if (_postMappingFunction != null) _postMappingFunction(MapType.Remove, default(TInputType), listType);
            }
        }

        protected virtual void MapExistingItems(TInputType fromItem, TListType toListItem)
        {
            var allow = true;
            if (_preMappingFunction != null) allow = _preMappingFunction(MapType.Map, fromItem, toListItem);
            if (allow)
            {
                _mappingAction(fromItem, toListItem);
                if (_postMappingFunction != null) _postMappingFunction(MapType.Map, fromItem, toListItem);
            }
        }

        protected virtual void AddMap(ICollection<TListType> toList, TInputType fromItem)
        {
            var allow = true;
            if (_preMappingFunction != null) allow = _preMappingFunction(MapType.Add, fromItem, default(TListType));
            if (allow)
            {
                var listType = _mappingAction(fromItem, default(TListType));
                toList.Add(listType);
                if (_postMappingFunction != null) _postMappingFunction(MapType.Add, fromItem, listType);
            }

        }

        protected virtual bool ItemEquality(TInputType fromItem, TListType toListItem)
        {
            if (_equality != null) return _equality(fromItem, toListItem);
            return fromItem.Equals(toListItem);
        }

        #endregion

        

       
    }
}